#include <cstdio>
#include <random>
#include <sysc/kernel/sc_time.h>
#include <systemc>
#include <tlm>
#include <tlm_utils/simple_initiator_socket.h>
#include <tlm_utils/simple_target_socket.h>
#include <tlm_utils/tlm_quantumkeeper.h>

using namespace sc_core;
using namespace tlm;
using namespace tlm_utils;

// Initiator Module demonstrating tlm_quantumkeeper
struct Initiator : sc_module {
    simple_initiator_socket<Initiator> socket;
    tlm_quantumkeeper qk;

    SC_CTOR(Initiator) : socket("isocket") {
        SC_THREAD(thread_process);
        qk.set_global_quantum(sc_time(100, SC_NS));  // Set global quantum to 100 ns
        qk.reset(); // Reset quantum keeper
    }

    void thread_process() {
        tlm_generic_payload trans;
        sc_time delay;

        for (int i = 0; i < 128; ++i) {
            trans.set_command(TLM_WRITE_COMMAND);
            trans.set_address(i);
            unsigned char data = i;
            trans.set_data_ptr(&data);
            trans.set_data_length(1);

            delay = qk.get_local_time();  // Get current local time

            socket->b_transport(trans, delay);

            qk.set(delay);  // Update local time with returned delay
            printf("Transaction %d, local_time=%s, global_time=%s\n", i, qk.get_local_time().to_string().c_str(), sc_time_stamp().to_string().c_str());

            if (qk.need_sync()) {
                printf("Synchronized, local_time=%s, global_time=%s, compute_local_quantum=%s\n", 
                    qk.get_local_time().to_string().c_str(), sc_time_stamp().to_string().c_str(), 
                    tlm_global_quantum::instance().compute_local_quantum().to_string().c_str());
                qk.sync();  // Sync with SystemC time if necessary
            }
        }
    }
};

// Simple Target Module
struct Target : sc_module {
    simple_target_socket<Target> socket;
    std::mt19937 gen;
    std::uniform_int_distribution<> dist;
    SC_CTOR(Target) : socket("tsocket"), gen(1), dist(2, 20) {
        socket.register_b_transport(this, &Target::b_transport);
    }

    void b_transport(tlm_generic_payload& trans, sc_time& delay) {
        trans.set_response_status(TLM_OK_RESPONSE);
        // Simulate some processing delay
        sc_time delta = sc_time(dist(gen), SC_NS);
        delay += delta;
        printf("Target processing delay: %s\n", delta.to_string().c_str());
    }
};

int sc_main(int argc, char* argv[]) {
    Initiator initiator("initiator");
    Target target("target");

    initiator.socket.bind(target.socket);

    sc_start();

    return 0;
}
